﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace PrattParsing
{
    public interface PrefixParselet<E>
    {
        E parse(PrattParser<E> parser, Token token);
    }
    public interface InfixParselet<E>
    {
        E parse(PrattParser<E> parser, E left, Token token);
        int precedence();
    }

    [DebuggerNonUserCode]
    public static class Parselet
    {
        public delegate E PrefixParseFunc<E>(PrattParser<E> parser, Token token);
        public delegate E InfixParseFunc<E>(PrattParser<E> parser, E left, Token token, int callprec);
        public delegate E InfixComputeFunc<E>(E left, Token op, E right);

        /// <summary>
        /// Generates a prefix parselet that uses the specified lambda function as the parse function.
        /// </summary>
        /// <typeparam name="E">The expression type.</typeparam>
        /// <param name="parse">The lambda parse function. Parser<E>, Token -> E</param>
        /// <returns>A prefix parselet.</returns>
        public static PrefixParselet<E> Lambda<E>(PrefixParseFunc<E> parse)
        {
            return new LambdaPrefixParselet<E>(parse);
        }
        /// <summary>
        /// Generates an infix parselet that uses the specified lambda function as the parse function.
        /// </summary>
        /// <typeparam name="E">The expression type.</typeparam>
        /// <param name="prec">The precedence of the infix token.</param>
        /// <param name="assoc">The associativity of the infix token.</param>
        /// <param name="parse">The lambda parse function. Parser<E>, E, Token -> E</param>
        /// <returns>An infix parselet.</returns>
        public static InfixParselet<E> Lambda<E>(int prec, bool assoc, InfixParseFunc<E> parse)
        {
            return new LambdaInfixParselet<E>(prec, assoc, parse);
        }
        /// <summary>
        /// Generates an infix parselet that uses the specified lambda function to compute a result.
        /// </summary>
        /// <typeparam name="E">The expression type.</typeparam>
        /// <param name="prec">The precedence of the infix operator.</param>
        /// <param name="assoc">The associativity of the infix operator.</param>
        /// <param name="compute">The computing function. E, Token, E -> E</param>
        /// <returns>An infix parselet.</returns>
        public static InfixParselet<E> Lambda<E>(int prec, bool assoc, InfixComputeFunc<E> compute)
        {
            return new LambdaInfixComputeParselet<E>(prec, assoc, compute);
        }

        [DebuggerNonUserCode]
        private class LambdaPrefixParselet<E> : PrefixParselet<E>
        {
            PrefixParseFunc<E> func;

            public LambdaPrefixParselet(PrefixParseFunc<E> func)
            {
                this.func = func;
            }

            public E parse(PrattParser<E> parser, Token token)
            {
                return func(parser, token);
            }
        }
        [DebuggerNonUserCode]
        private class LambdaInfixParselet<E> : InfixOperatorParselet<E>
        {
            InfixParseFunc<E> func;

            public LambdaInfixParselet(int prec, bool assoc, InfixParseFunc<E> func)
                : base(prec, assoc)
            {
                this.func = func;
            }

            public override E parse(PrattParser<E> parser, E left, Token token)
            {
                return func(parser, left, token, callprec);
            }
        }
        [DebuggerNonUserCode]
        private class LambdaInfixComputeParselet<E> : InfixOperatorParselet<E>
        {
            InfixComputeFunc<E> func;

            public LambdaInfixComputeParselet(int prec, bool assoc, InfixComputeFunc<E> func)
                : base(prec, assoc)
            {
                this.func = func;
            }

            public override E parse(PrattParser<E> parser, E left, Token token)
            {
                return func(left, token, parser.parse(callprec));
            }
        }
    }
}
